/*
* Copyright [2021] JD.com, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "da_string.h"
#include <stdlib.h>
#include <string.h>

/*
 * String (struct string) is a sequence of unsigned char objects terminated
 * by the null character '\0'. The length of the string is pre-computed and
 * made available explicitly as an additional field. This means that we don't
 * have to walk the entire character sequence until the null terminating
 * character everytime that the length of the String is requested
 *
 * The only way to create a String is to initialize it using, string_init()
 * and duplicate an existing String - string_duplicate() or copy an existing
 * raw sequence of character bytes - string_copy(). Such String's must be
 * freed using string_deinit()
 *
 * We can also create String as reference to raw string - string_set_raw()
 * or to text string - string_set_text() or string(). Such String don't have
 * to be freed.
 */

void string_init(struct string *str) {
	str->len = 0;
	str->data = NULL;
}

void string_deinit(struct string *str)
{
    ASSERT((str->len == 0 && str->data == NULL) ||
           (str->len != 0 && str->data != NULL));

    if (str->data != NULL) {
        free(str->data);
        string_init(str);
    }
}

bool string_empty(const struct string *str) {
	ASSERT((str->len == 0 && str->data == NULL)|| (str->len != 0 && str->data != NULL));
	return str->len == 0 ? true : false;
}

int string_duplicate(struct string *dst, const struct string *src)
{
    ASSERT(dst->len == 0 && dst->data == NULL);
    ASSERT(src->len != 0 && src->data != NULL);

    dst->data = da_strndup(src->data, src->len + 1);
    if (dst->data == NULL) {
        return -1;
    }
    dst->len = src->len;
    dst->data[dst->len] = '\0';

    return 0;
}

int string_copy(struct string *dst, const uint8_t *src, uint32_t srclen)
{
    ASSERT(dst->len == 0 && dst->data == NULL);
    ASSERT(src != NULL && srclen != 0);

    dst->data = da_strndup(src, srclen + 1);
    if (dst->data == NULL) {
        return -1;
    }
    dst->len = srclen;
    dst->data[dst->len] = '\0';

    return 0;
}

int string_compare(const struct string *s1, const struct string *s2) {
	if (s1->len != s2->len) {
		return s1->len > s2->len ? 1 : -1;
	}
	return da_strncmp(s1->data, s2->data, s1->len);
}

static const char *
_safe_check_longlong(const char *fmt, int32_t * have_longlong) {
	*have_longlong = false;
	if (*fmt == 'l') {
		fmt++;
		if (*fmt != 'l') {
			*have_longlong = (sizeof(long) == sizeof(int64_t));
		} else {
			fmt++;
			*have_longlong = true;
		}
	}
	return fmt;
}

static char *
_safe_utoa(int _base, uint64_t val, char *buf) {
	char hex[] = "0123456789abcdef";
	uint32_t base = (uint32_t) _base;
	*buf-- = 0;
	do {
		*buf-- = hex[val % base];
	} while ((val /= base) != 0);
	return buf + 1;
}

static char *
_safe_itoa(int base, int64_t val, char *buf) {
	char hex[] = "0123456789abcdef";
	char *orig_buf = buf;
	const int32_t is_neg = (val < 0);
	*buf-- = 0;

	if (is_neg) {
		val = -val;
	}
	if (is_neg && base == 16) {
		int ix;
		val -= 1;
		for (ix = 0; ix < 16; ++ix)
			buf[-ix] = '0';
	}

	do {
		*buf-- = hex[val % base];
	} while ((val /= base) != 0);

	if (is_neg && base == 10) {
		*buf-- = '-';
	}

	if (is_neg && base == 16) {
		int ix;
		buf = orig_buf - 1;
		for (ix = 0; ix < 16; ++ix, --buf) {
			/* *INDENT-OFF* */
			switch (*buf) {
			case '0':
				*buf = 'f';
				break;
			case '1':
				*buf = 'e';
				break;
			case '2':
				*buf = 'd';
				break;
			case '3':
				*buf = 'c';
				break;
			case '4':
				*buf = 'b';
				break;
			case '5':
				*buf = 'a';
				break;
			case '6':
				*buf = '9';
				break;
			case '7':
				*buf = '8';
				break;
			case '8':
				*buf = '7';
				break;
			case '9':
				*buf = '6';
				break;
			case 'a':
				*buf = '5';
				break;
			case 'b':
				*buf = '4';
				break;
			case 'c':
				*buf = '3';
				break;
			case 'd':
				*buf = '2';
				break;
			case 'e':
				*buf = '1';
				break;
			case 'f':
				*buf = '0';
				break;
			}
			/* *INDENT-ON* */
		}
	}
	return buf + 1;
}

int _safe_vsnprintf(char *to, size_t size, const char *format, va_list ap) {
	char *start = to;
	char *end = start + size - 1;
	for (; *format; ++format) {
		int32_t have_longlong = false;
		if (*format != '%') {
			if (to == end) { /* end of buffer */
				break;
			}
			*to++ = *format; /* copy ordinary char */
			continue;
		}
		++format; /* skip '%' */

		format = _safe_check_longlong(format, &have_longlong);

		switch (*format) {
		case 'd':
		case 'i':
		case 'u':
		case 'x':
		case 'p': {
			int64_t ival = 0;
			uint64_t uval = 0;
			if (*format == 'p')
				have_longlong = (sizeof(void *) == sizeof(uint64_t));
			if (have_longlong) {
				if (*format == 'u') {
					uval = va_arg(ap, uint64_t);
				} else {
					ival = va_arg(ap, int64_t);
				}
			} else {
				if (*format == 'u') {
					uval = va_arg(ap, uint32_t);
				} else {
					ival = va_arg(ap, int32_t);
				}
			}

			{
				char buff[22];
				const int base = (*format == 'x' || *format == 'p') ? 16 : 10;

				/* *INDENT-OFF* */
				char *val_as_str =
						(*format == 'u') ?
								_safe_utoa(base, uval,
										&buff[sizeof(buff) - 1]) :
								_safe_itoa(base, ival, &buff[sizeof(buff) - 1]);
				/* *INDENT-ON* */

				/* Strip off "ffffffff" if we have 'x' format without 'll' */
				if (*format == 'x' && !have_longlong && ival < 0) {
					val_as_str += 8;
				}

				while (*val_as_str && to < end) {
					*to++ = *val_as_str++;
				}
				continue;
			}
		}
		case 's': {
			const char *val = va_arg(ap, char *);
			if (!val) {
				val = "(null)";
			}
			while (*val && to < end) {
				*to++ = *val++;
			}
			continue;
		}
		}
	}
	*to = 0;
	return (int) (to - start);
}

int _safe_snprintf(char *to, size_t n, const char *fmt, ...) {
	int result;
	va_list args;
	va_start(args, fmt);
	result = _safe_vsnprintf(to, n, fmt, args);
	va_end(args);
	return result;
}
